package edu.hawaii.halealohacli.kmj;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.regex.Pattern;
import javax.xml.datatype.DatatypeConfigurationException;
import org.junit.Test;
import org.wattdepot.client.BadXmlException;
import org.wattdepot.client.MiscClientException;
import org.wattdepot.client.NotAuthorizedException;
import org.wattdepot.client.ResourceNotFoundException;
import org.wattdepot.client.WattDepotClient;

/**
 * Tests the various methods of the MonitorGoal class, the MonitorGoalTask class, and the changes in
 * the CommandParser class to add the monitor-goal command to the system.
 * 
 * @author Christopher Foo
 * 
 */
public class TestMonitorGoal {

  /**
   * The URL of the WattDepot server to connect to.
   */
  private String url = "http://server.wattdepot.org:8190/wattdepot/";

  /**
   * Client connected to the WattDepot server that can be used to query the server.
   */
  private WattDepotClient client = new WattDepotClient(this.url);

  /**
   * The name of the tower used to test the various methods.
   */
  private String tower = "Lokelani";

  /**
   * Test that the monitor-goal command can be called from the CommandParser and that it
   * successfully rejects invalid input or situations without calling the command (and hence making
   * it crash). Does not check the successful case as this would require user input to terminate the
   * command.
   */
  @Test
  public void testCommandParserMethod() {
    CommandParser parser = new CommandParser(client);
    try {

      // No arguments
      parser.commandCheck("monitor-goal");

      // Valid input without Baseline set
      parser.commandCheck("monitor-goal Lehua 10 10");

      // Valid input with wrong Baseline set
      parser.commandCheck("set-baseline Lokelani");
      parser.commandCheck("monitor-goal Lehua 10 10");

      // Set proper Baseline for rest of the tests
      parser.commandCheck("set-baseline Lehua");

      // 1 argument
      parser.commandCheck("monitor-goal Lehua");

      // 2 arguments
      parser.commandCheck("monitor-goal Lehua 10");

      // Invalid Tower/Lounge
      parser.commandCheck("monitor-goal FAKE-TOWER 10 10");

      // Invalid goal
      parser.commandCheck("monitor-goal Lehua 100 10");
      parser.commandCheck("monitor-goal Lehua 0 10");

      // Invalid interval
      parser.commandCheck("monitor-goal Lehua 10 -1");
    }
    catch (DatatypeConfigurationException e) {
      fail("commandCheck threw DatatypeConfigurationException");
    }
  }

  /**
   * Tests that the getInterval method returns the interval field of the MonitorGoal object as
   * expected.
   */
  @Test
  public void testGetInterval() {

    // Test with default value
    MonitorGoal command = new MonitorGoal(null, null, 0, 10, null);
    assertEquals("MonitorGoal with interval of 10 should return 10.", command.getInterval(), 10);

    // Test with large value
    command = new MonitorGoal(null, null, 0, Integer.MAX_VALUE, null);
    assertEquals(
        "MonitorGoal with interval of max integer value should return the max integer value.",
        command.getInterval(), Integer.MAX_VALUE);

    // Test with 0
    command = new MonitorGoal(null, null, 0, 0, null);
    assertEquals("MonitorGoal with interval of 0 should return 0.", command.getInterval(), 0);
  }

  /**
   * Test that the printResult method of MonitorGoal runs without any errors when told to stop.
   * Cannot automatically test when not told to stop due to dependence on user input to stop the
   * repeating task once it has started.
   */
  @Test
  public void testPrintResult() {
    Baseline baseline;
    try {
      baseline = new Baseline(this.client, this.client.getSource(this.tower), "2011-12-09");
      MonitorGoal command = new MonitorGoal(this.client, this.tower, 1, 10, baseline);
      command.setStop(true);
      command.printResult();
    }
    catch (NotAuthorizedException e) {
      fail("Baseline threw NotAuthorizedException");
    }
    catch (ResourceNotFoundException e) {
      fail("Baseline threw ResourceNotFoundException");
    }
    catch (BadXmlException e) {
      fail("Baseline threw BadXmlException");
    }
    catch (MiscClientException e) {
      fail("Baseline threw MiscClientException");
    }
    catch (Exception e) {
      fail("Baseline threw Exception");
    }
  }

  /**
   * Tests that the getCurrentPower method of the MonitorGoalTask class returns the latest power
   * consumed which should be larger than 0.
   */
  @Test
  public void testCurrentPower() {
    MonitorGoal command = new MonitorGoal(this.client, this.tower, 0, 0, null);
    MonitorGoalTask task = new MonitorGoalTask(command);
    try {
      double currentPower = task.getCurrentPower();
      assertTrue("Returned power should be larger than 0.", currentPower > 0);
    }
    catch (NotAuthorizedException e) {
      fail("getCurrentPower threw NotAuthorizedException");
    }
    catch (ResourceNotFoundException e) {
      fail("getCurrentPower threw ResourceNotFoundException");
    }
    catch (BadXmlException e) {
      fail("getCurrentPower threw BadXmlException");
    }
    catch (MiscClientException e) {
      fail("getCurrentPower threw MiscClientException");
    }
  }

  /**
   * Tests the meetsGoal method of the MonitorGoalTask command which should return whether the
   * current power value meets the specified goal or not.
   */
  @Test
  public void testMeetsGoal() {
    boolean met, metBoundary, notMet, notMetBoundary;
    Baseline baseline = new Baseline();
    double[] values =
        { 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0,
            10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0 };
    baseline.setBaselineValues(values);
    MonitorGoal command = new MonitorGoal(this.client, this.tower, 10, 0, baseline);
    MonitorGoalTask task = new MonitorGoalTask(command);
    met = task.meetsGoal(8);
    metBoundary = task.meetsGoal(8.999999);
    notMet = task.meetsGoal(10);
    notMetBoundary = task.meetsGoal(9);

    assertTrue("Below goal should meet goal.", met);
    assertTrue("Just below goal should meet goal.", metBoundary);
    assertFalse("Above goal should not meet goal.", notMet);
    assertFalse("On the goal should not meet goal (must be below goal).", notMetBoundary);
  }

  /**
   * Tests that the return value from the generateResultString method in the MonitorGoalTask class
   * matches the expected pattern.
   */
  @Test
  public void testGenerateResultString() {
    String regex = ".+\t[\\d\\.]+ kW\tGoal (Not )?Met!\n";
    boolean matchTower = false;
    boolean matchLounge = false;
    boolean matchNotMet = false;
    boolean matchMet = false;
    double[] values1 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    double[] values2 =
        { Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE,
            Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE,
            Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE,
            Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE,
            Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE,
            Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE };
    String returnString;
    Baseline baseline;
    MonitorGoal command;
    MonitorGoalTask task;

    // Test at tower level
    try {
      baseline = new Baseline(this.client, this.client.getSource(this.tower), "2011-12-09");
      command = new MonitorGoal(this.client, this.tower, 1, 10, baseline);
      task = new MonitorGoalTask(command);
      returnString = task.generateResultString();
      matchTower = Pattern.matches(regex, returnString);
    }
    catch (NotAuthorizedException e) {
      fail("Baseline threw NotAuthorizedException");
    }
    catch (ResourceNotFoundException e) {
      fail("Baseline threw ResourceNotFoundException");
    }
    catch (BadXmlException e) {
      fail("Baseline threw BadXmlException");
    }
    catch (MiscClientException e) {
      fail("Baseline threw MiscClientException");
    }
    catch (Exception e) {
      fail("Baseline threw Exception");
    }

    // Test at lounge level
    try {
      baseline = new Baseline(this.client, this.client.getSource("Lehua-D"), "2011-12-09");
      command = new MonitorGoal(this.client, "Lehua-D", 1, 10, baseline);
      task = new MonitorGoalTask(command);
      returnString = task.generateResultString();
      matchLounge = Pattern.matches(regex, returnString);
    }
    catch (NotAuthorizedException e) {
      fail("Baseline threw NotAuthorizedException");
    }
    catch (ResourceNotFoundException e) {
      fail("Baseline threw ResourceNotFoundException");
    }
    catch (BadXmlException e) {
      fail("Baseline threw BadXmlException");
    }
    catch (MiscClientException e) {
      fail("Baseline threw MiscClientException");
    }
    catch (Exception e) {
      fail("Baseline threw Exception");
    }

    // Test with not met goal

    baseline = new Baseline();
    baseline.setBaselineValues(values1);
    command = new MonitorGoal(this.client, this.tower, 90, 10, baseline);
    task = new MonitorGoalTask(command);
    returnString = task.generateResultString();
    matchNotMet = Pattern.matches(regex, returnString);

    // Test with met goal
    baseline = new Baseline();
    baseline.setBaselineValues(values2);
    command = new MonitorGoal(this.client, this.tower, 1, 10, baseline);
    task = new MonitorGoalTask(command);
    returnString = task.generateResultString();
    matchMet = Pattern.matches(regex, returnString);

    assertTrue("Result string from tower query did not fit pattern.", matchTower);
    assertTrue("Result string from lounge query fit pattern.", matchLounge);
    assertTrue("Result string that did not meet the goal did not fit pattern.", matchNotMet);
    assertTrue("Result string that met the goal did not fit pattern.", matchMet);
  }

  /**
   * Tests that the string representation of the integer given to the MonitorGoalTask's adjustInt
   * method is adjusted as expected.
   */
  @Test
  public void testAdjustInt() {
    MonitorGoal command = new MonitorGoal(null, null, 0, 0, null);
    MonitorGoalTask task = new MonitorGoalTask(command);
    assertTrue("0 should be adjusted to \"00\"", task.adjustInt(0).equals("00"));
    assertTrue("9 should be adjusted to \"09\"", task.adjustInt(9).equals("09"));
    assertTrue("10 should be adjusted to \"10\"", task.adjustInt(10).equals("10"));
    assertTrue("99 should be adjusted to \"99\"", task.adjustInt(99).equals("99"));
  }
}
